// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FIX42OrderPlugin.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge.Plugins
{
    #region Using Directives

    using System;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Events;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LiveObjects;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    using QuickFix;

    #endregion

    /// <summary>
    /// The fix 42 order plugin.
    /// </summary>
    [Credits("FIX42 Order Plugin", "Handles all order operations using FIX 4.2", "David Denis", 
        "Copyright (C) David Denis <mailto:david.denis@systemathics.com>")]
    [ImplementsInterface("Org.OpenTrader.Framework.Forge.Interfaces.IOrderPlugin")]
    [ConfigurationField("OrderRoutingConfigurationSchema", "Symbol", typeof(ConfigurationField.String), "Symbol", IsMandatory = true, DefaultValue = "Symbol")]
    public sealed partial class FIX42OrderPlugin : FixOrderPlugin
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        /// <summary>
        /// The handlers store.
        /// </summary>
        private readonly HandlersStore handlersStore = new HandlersStore();

        /// <summary>
        /// The orders store.
        /// </summary>
        private readonly OrdersStore<ClOrdID> ordersStore = new OrdersStore<ClOrdID>();

        /// <summary>
        /// The application.
        /// </summary>
        private FIX42Application application;

        /// <summary>
        /// The connection.
        /// </summary>
        private FIXConnection connection;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="FIX42OrderPlugin"/> class.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public FIX42OrderPlugin(ILiveObjectDescriptor descriptor, ILiveObjectContainer container)
            : base(descriptor, container)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets ConnectionStatus.
        /// </summary>
        public override EConnectionStatus ConnectionStatus
        {
            get
            {
                return this.connection.ConnectionStatus;
            }
        }

        /// <summary>
        /// Gets ConnectionURL.
        /// </summary>
        public override string ConnectionURL
        {
            get
            {
                return this.connection.ToString();
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Cancel the order.
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public override Status Cancel(IOrderWiring wiring, IOrder order, RemotableEventHandler<OrderEventArgs> handler)
        {
            // Not sent by us
            if (!this.ordersStore.HasOrder(order.ID))
            {
                return Status.Failure(Identity, "FIX42OrderPlugin: This order isn't known, cannot cancel it !");
            }

            // No connection available
            if (this.connection.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "FIX42OrderPlugin: connection Status is not EConnectionStatus.Connected");
            }

            // Keep the returning handler for this order wiring
            this.handlersStore.SetHandler(order.ID, handler);

            // Id of the FIX request 
            var origId = new OrigClOrdID(order.ID.ToString());
            var newId = new ClOrdID(order.ID.ToString() + "-Cancel");
            try
            {
                // Mark it manual
                var handleInst = new HandlInst(HandlInst.MANUAL_ORDER);

                // Here is the symbol
                var symbol = new Symbol(wiring.OrderRoutingConfiguration["Symbol"]);

                // Way
                var side = new Side(order.Way == EOrderWay.Buy ? Side.BUY : Side.SELL);

                // Stamp
                var transactTime = new TransactTime(Timings.Now, true);

                // Type
                var type = new OrdType(OrdType.LIMIT_OR_BETTER);

                // Create the order request message
                var nos = new QuickFix42.OrderCancelRequest(origId, newId, symbol, side, transactTime);

                // Set Size
                nos.set(new OrderQty(order.Quantity));

                // Send request
                Session.sendToTarget(nos, this.connection.SessionID);
            }
            catch (Exception e)
            {
                return Status.Failure(Identity, string.Format("FIX42OrderPlugin: Got Exception {0}", e));
            }

            // Add request entries
            this.ordersStore.SetOrder(order, newId);

            // Success
            return Status.Success(Identity);
        }

        /// <summary>
        /// The connect.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Connect()
        {
            return this.connection.Connect();
        }

        /// <summary>
        /// The disconnect.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Disconnect()
        {
            return this.connection.Disconnect();
        }

        /// <summary>
        /// Modify the order
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <param name="newOrder">
        /// The newOrder.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public override Status Modify(IOrderWiring wiring, IOrder order, IOrder newOrder, RemotableEventHandler<OrderEventArgs> handler)
        {
            // Not sent by us
            if (!this.ordersStore.HasOrder(order.ID))
            {
                return Status.Failure(Identity, "FIX42OrderPlugin: This order isn't known, cannot cancel it !");
            }

            // No connection available
            if (this.connection.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "FIX42OrderPlugin: connection Status is not EConnectionStatus.Connected");
            }

            // Keep the returning handler for this order wiring
            this.handlersStore.SetHandler(order.ID, handler);

            // Id of the FIX request 
            var origId = new OrigClOrdID(order.ID.ToString());
            var newId = new ClOrdID(order.ID.ToString() + "-Modify");
            try
            {
                // Mark it manual
                var handleInst = new HandlInst(HandlInst.MANUAL_ORDER);

                // Here is the symbol
                var symbol = new Symbol(wiring.OrderRoutingConfiguration["Symbol"]);

                // Way
                var side = new Side(order.Way == EOrderWay.Buy ? Side.BUY : Side.SELL);

                // Stamp
                var transactTime = new TransactTime(order.Stamp, true);

                // Type
                var type = new OrdType(OrdType.LIMIT_OR_BETTER);

                // Create the order request message
                var nos = new QuickFix42.OrderCancelReplaceRequest(origId, newId, handleInst, symbol, side, transactTime, type);

                // Set Size
                nos.set(new OrderQty(newOrder.Price));

                // Set value
                nos.set(new Price(newOrder.Quantity));

                // Send request
                Session.sendToTarget(nos, this.connection.SessionID);
            }
            catch (Exception e)
            {
                return Status.Failure(Identity, string.Format("FIX42OrderPlugin: Got Exception {0}", e));
            }

            // Add request entries
            this.ordersStore.SetOrder(order, newId);

            // Success
            return Status.Success(Identity);
        }

        /// <summary>
        /// The on activation context.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="activationEventArgs">
        /// The activation Context.
        /// </param>
        public override void OnActivation(object sender, ActivationEventArgs activationEventArgs)
        {
            if (activationEventArgs is ActivationEventArgs.CreateDestroy)
            {
                switch (activationEventArgs.Type)
                {
                    case EActivationType.Activation:
                        this.application = new FIX42Application(this);
                        this.connection = new FIXConnection(
                            this, 
                            EFIXVersion.FIX42, 
                            EFIXPluginType.FIXOrderPlugin, 
                            this.application, 
                            this.Configuration["Host"], 
                            this.Configuration["Port"], 
                            this.Configuration["SenderCompID"], 
                            this.Configuration["TargetCompID"], 
                            this.Configuration["ODBCConnectionString"]);
                        break;

                    case EActivationType.Desactivation:
                        this.handlersStore.Clear();
                        this.ordersStore.Clear();
                        break;
                }
            }
        }

        /// <summary>
        /// Send the order
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public override Status Send(IOrderWiring wiring, IOrder order, RemotableEventHandler<OrderEventArgs> handler)
        {
            // No connection available
            if (this.connection.ConnectionStatus != EConnectionStatus.Connected)
            {
                return Status.Failure(Identity, "FIX42OrderPlugin: connection Status is not EConnectionStatus.Connected");
            }

            // Keep the returning handler for this order wiring
            this.handlersStore.SetHandler(order.ID, handler);

            // Id of the FIX request
            var id = new ClOrdID(order.ID.ToString());
            try
            {
                // Mark it manual
                var handleInst = new HandlInst(HandlInst.MANUAL_ORDER);

                // Here is the symbol
                var symbol = new Symbol(wiring.OrderRoutingConfiguration["Symbol"]);

                // Way
                var side = new Side(order.Way == EOrderWay.Buy ? Side.BUY : Side.SELL);

                // Stamp
                var transactTime = new TransactTime(Timings.Now, true);

                // Type
                OrdType type;
                TimeInForce force;
                switch (order.Type)
                {
                    case EOrderType.Limit:
                        type = new OrdType(OrdType.LIMIT);
                        force = new TimeInForce(TimeInForce.GOOD_TILL_CANCEL);
                        break;

                    case EOrderType.FillOrKill:
                        type = new OrdType(OrdType.LIMIT);
                        force = new TimeInForce(TimeInForce.FILL_OR_KILL);
                        break;

                    default:
                        return Status.Failure(Identity, "FIX42OrderPlugin: Only Supports EOrderType.Limit and EOrderType.FillOrKill Order Types");
                }

                // Create the order request message
                var nos = new QuickFix42.NewOrderSingle(id, handleInst, symbol, side, transactTime, type);

                // Time in force (tag 52)
                nos.setField(52, string.Empty + force.getValue());

                // Set price
                nos.set(new OrderQty(order.Quantity));

                // Set value
                nos.set(new Price(order.Price));

                // Send request
                Session.sendToTarget(nos, this.connection.SessionID);
            }
            catch (Exception e)
            {
                return Status.Failure(Identity, string.Format("FIX42OrderPlugin: Got Exception {0}", e));
            }

            // Add request entries
            this.ordersStore.SetOrder(order, id);

            // Success
            return Status.Success(Identity);
        }

        #endregion
    }
}